﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Net;
using System.IO;

namespace Space_Conquest
{
    class GameEngine : IMenu
    {

        #region Private Members

        private Map _map;
        private UnitManager _unitManager;
        private OnScreenDisplay _onScreenDisplay;
        private AnimationManager _animationManager;
        private ChatManager _chatManager;

        private InputManager _inputManager = new InputManager();
        private ChatBox _chatBox;

        public List<Player> _players = new List<Player>();
        private Player _localPlayer;
        private Player _workingPlayer;

        private Queue<GameEvent> _eventQueue = new Queue<GameEvent>();
        private bool _isMultiplayer;
        private bool _isServer;
        private SequentialServer _server;
        private SequentialClient _client;

        private bool _gameHasStarted;
        private MultiplayerStatus _multiplayerStatus;
        private string _myLanIP;
        private MultiplayerResponse _clientResponse;
        public string _mapHash;
        public string _mapName;
        public bool _clientFirstStatus;
        private int totalGameTicks = 0;

        Sprite _sprSelected = new Sprite(0, 0, 64, 64, 150, true);
        Texture _texSelected = new Texture("Images/SelectedUnit.bmp");

        #endregion

        #region Properties

        public Map Map
        {
            get
            {
                return _map;
            }
        }

        public UnitManager UnitManager
        {
            get
            {
                return _unitManager;
            }
        }

        public AnimationManager AnimationManager
        {
            get
            {
                return _animationManager;
            }
        }

        public List<Player> Players
        {
            get
            {
                return _players;
            }
        }

        public Player LocalPlayer
        {
            get
            {
                return _localPlayer;
            }
        }

        public Player WorkingPlayer
        {
            get
            {
                return _workingPlayer;
            }
        }

        public bool GameHasStarted
        {
            get
            {
                return _gameHasStarted;
            }
            set
            {
                if (_gameHasStarted == false)
                    _gameHasStarted = value;
            }
        }

        public MultiplayerStatus MultiplayerStatus
        {
            get
            {
                return _multiplayerStatus;
            }
        }

        public OnScreenDisplay OnScreenDisplay
        {
            get
            {
                return _onScreenDisplay;
            }
        }

        public bool IsServer
        {
            get
            {
                return _isServer;
            }
        }

        public bool IsMuliplayer
        {
            get
            {
                return _isMultiplayer;
            }
        }

        public ChatManager ChatManager
        {
            get
            {
                return _chatManager;
            }
        }

        #endregion

        #region Constructors

        public GameEngine(GameInitializer gameInitializer)
        {


            // Check if Initial Settings are valid
            gameInitializer.ValidateSettings();

            // Pull out and save useful settings
            _isMultiplayer = gameInitializer.IsMultiplayer;
            _isServer = gameInitializer.IsServer;

            // Set up Server and Client if needed
            if (_isMultiplayer)
            {

                _myLanIP = Dns.GetHostByName(Dns.GetHostName()).AddressList[0].ToString();

                _gameHasStarted = false;
                _multiplayerStatus = null;

                if (_isServer)
                {

                    _mapName = gameInitializer.MapFileName;
                    _mapHash = MD5.HashFile(Globals.RESOURCE_DIRECTORY + @"Maps\" + gameInitializer.MapFileName + ".map");

                    _multiplayerStatus = new MultiplayerStatus();
                    _multiplayerStatus.PlayerNames.Add(gameInitializer.PlayerNames[0]);

                    _server = new SequentialServer();
                    _server.OnReceiveData += new ReceivedDataEventHandler(_server_OnReceiveData);

                    _client = new SequentialClient("127.0.0.1");
                    _client.OnReceiveData += new ReceivedDataEventHandler(_client_OnReceiveData);

                }
                else
                {

                    _clientFirstStatus = true;

                    _client = new SequentialClient(gameInitializer.ServerIP);
                    _client.OnReceiveData += new ReceivedDataEventHandler(_client_OnReceiveData);

                    MultiplayerRequest request = new MultiplayerRequest();

                    request.MyIP = _myLanIP;
                    request.PlayerName = gameInitializer.PlayerNames[0];

                    _client.SendData(request);

                    _clientResponse = null;

                    while (_clientResponse == null) // Not recieved data
                    {
                        Thread.Sleep(100);
                        Application.DoEvents();
                    }

                    if (!_clientResponse.AcceptedRequest)
                    {
                        // Back up?
                    }

                }

            }
            else
            {
                _gameHasStarted = true;
            }

            if (!_isMultiplayer)
            {

                // Initialize players
                for (int i = 0; i < gameInitializer.NumberOfPlayers; ++i)
                    _players.Add(new Player(this, i, gameInitializer.PlayerNames[i]));

            }
            else if (_isServer)
            {
                _players.Add(new Player(this, 0, gameInitializer.PlayerNames[0]));
            }
            else
            {

                while (_multiplayerStatus == null)
                {
                    Thread.Sleep(100);
                    Application.DoEvents();
                }

                // Initialize players
                for (int i = 0; i < _multiplayerStatus.PlayerNames.Count; ++i)
                    _players.Add(new Player(this, i, _multiplayerStatus.PlayerNames[i]));

            }

            if (gameInitializer.IsServer || (!gameInitializer.IsMultiplayer))
            {
                _localPlayer = _players[0];
            }
            else
            {
                _localPlayer = _players[_clientResponse.PlayerNumber];
            }

            if (gameInitializer.IsMapEditor)
            {
                _map = new MapEditor(this, Globals.iMapEditorTilesX, Globals.iMapEditorTilesY);
            }
            else
            {
                if ((!_isMultiplayer) || _isServer)
                {
                    _map = new MapLoader(this, gameInitializer.MapFileName);
                }
                else
                {
                    _map = new MapLoader(this, _clientResponse.MapName, _clientResponse.MapHash);
                }
            }

            foreach (Player p in _players)
                p.InitializeFogArray(_map.WidthInTiles, _map.HeightInTiles);

            _unitManager = new UnitManager(this);
            _onScreenDisplay = new OnScreenDisplay(this);
            _animationManager = new AnimationManager(this);
            _chatManager = new ChatManager(this);
            _chatBox = new ChatBox(this, (Globals.iScreenWidth / 2) - 213, Globals.iScreenHeight - 215);

            Unit.Parent = _unitManager;

            _inputManager.Add(_unitManager);
            _inputManager.Add(_map);

            if (!gameInitializer.IsMapEditor)
            {
                // Past here is for testing

                _unitManager.AddUnit(new OilField(3, 2));
                _unitManager.AddUnit(new OilField(4, 2));
                _unitManager.AddUnit(new Metal(2, 2));
                _unitManager.AddUnit(new Metal(2, 3));
                _unitManager.AddUnit(new Metal(2, 4));

                _unitManager.AddUnit(new Metal(15, 16));
                _unitManager.AddUnit(new Metal(16, 16));
                _unitManager.AddUnit(new Metal(17, 16));

                for (int i = 0; i < _players.Count; ++i)
                    AddPlayerToMap(i);

            }

        }

        #endregion

        #region IMenu Members

        public void UnloadSelf()
        {

            _map = null;
            _unitManager = null;

            Globals.iScreenOffsetX = 0;
            Globals.iScreenOffsetY = 0;

            Globals.GameStack.Pop();

        }

        #endregion

        #region IRender Members

        public void Render()
        {

            _map.DetermineRenderBounds();

            _map.Render(RenderLayer.MapTiles);
            _animationManager.Render(RenderLayer.BelowGroundUnits);
            _map.Render(RenderLayer.GroundUnits);
            _animationManager.Render(RenderLayer.AboveGroundUnitsBelowAirUnits);
            _map.Render(RenderLayer.AirUnits);
            _animationManager.Render(RenderLayer.AboveAirUnits);

            _chatManager.Render();
            _onScreenDisplay.Render();
            _chatBox.Render();

        }

        #endregion

        #region IMotion Members

        public void Motion()
        {

            _chatManager.Motion();

            // Check if each player's selected unit should be set to null
            foreach (Player p in _players)
            {
                if (p.SelectedUnit != null)
                {
                    if(!p.SelectedUnit.IsAlive)
                    {
                        p.SelectedUnit = null;
                    }
                    else if (!(p.FogArray[p.SelectedUnit.MapPoint.X, p.SelectedUnit.MapPoint.Y] == FogStatus.Visible))
                    {
                        p.SelectedUnit = null;
                    }
                }
            }

            if (!_isMultiplayer)
            {
                _eventQueue.Enqueue(new MotionEvent());
            }
            else if (_isServer && _gameHasStarted)
            {
                //_eventQueue.Enqueue(new MotionEvent());
                _server.SendData(new MotionEvent());
            }


            try
            {

                // Queue work goes here...
                // Fire all events in queue up to a Motion Tick, or empty
                while (_eventQueue.Count > 0)
                {
                    if (_eventQueue.Peek() is MotionEvent)
                    {

                        _map.Motion();
                        _unitManager.Motion();
                        _animationManager.Motion();
                        _eventQueue.Dequeue();

                    }
                    else
                    {
                        ParseNextEvent();
                        _eventQueue.Dequeue();
                    }
                }
            }
            catch (Exception ex)
            {
                #if DEBUG
                Helpers.LogEvent(ex.TargetSite.ToString(), ex.Message);
                #endif
            }


        }

        #endregion

        #region IInput Members

        public bool MouseDown(System.Windows.Forms.MouseButtons button, int x, int y)
        {

            _onScreenDisplay.MouseDown(button, x + Globals.iScreenOffsetX, y + Globals.iScreenOffsetY);
            if (y < 32 || y > Globals.iScreenHeight - 172)
            {
                return true;
            }

            x += Globals.iScreenOffsetX;
            y += Globals.iScreenOffsetY;

            RedirectGameEvent(new MouseDownEvent(_localPlayer, button, x, y));
            return true;

        }

        public bool MouseUp(System.Windows.Forms.MouseButtons button, int x, int y)
        {

            x += Globals.iScreenOffsetX;
            y += Globals.iScreenOffsetY;

            RedirectGameEvent(new MouseUpEvent(_localPlayer, button, x, y));
            return true;

        }

        public bool MouseMove(int x, int y)
        {

            if (_inputManager.MouseMove(x, y))
                return true;

            if(_onScreenDisplay.MouseMove(x, y))
                return true;

            return _localPlayer.MouseMove(x, y);

        }

        public bool KeyDown(Keys key, bool shift)
        {


            if (_chatBox.KeyDown(key, shift))
                return true;

            #if DEBUG

            if (!_isMultiplayer)
            {
                switch (key)
                {
                    case Keys.F1:
                        _localPlayer = _players[0];
                        break;
                    case Keys.F2:
                        _localPlayer = _players[1];
                        break;
                    case Keys.F3:
                        _localPlayer = _players[2];
                        break;
                    case Keys.F4:
                        _localPlayer = _players[3];
                        break;
                }
            }
            #endif

            RedirectGameEvent(new KeyDownEvent(_localPlayer, key, shift));
            return true;
        }

        public bool KeyUp(Keys key, bool shift)
        {
            RedirectGameEvent(new KeyUpEvent(_localPlayer, key, shift));
            return true;
        }

        #endregion

        #region Methods

        public void RedirectGameEvent(GameEvent gameEvent)
        {

            if (!_isMultiplayer)
            {
                // Single Player, just send to Queue
                _eventQueue.Enqueue(gameEvent);
            }
            else if (_isServer)
            {
                // Is Server, add to it's queue then send to all clients
                //_eventQueue.Enqueue(gameEvent);
                _server.SendData(gameEvent);
            }
            else
            {
                // Is a client, send to server
                _client.SendData(gameEvent);
            }

        }

        void ParseNextEvent()
        {


            Player owner = _players[_eventQueue.Peek().PlayerID];
            _workingPlayer = owner;

            if (_eventQueue.Peek() is MouseDownEvent)
            {

                MouseDownEvent temp = (MouseDownEvent)(_eventQueue.Peek());

                if (_gameHasStarted)
                {

                    if (_inputManager.MouseDown(temp.Button, temp.X, temp.Y))
                        return;

                    owner.MouseDown(temp.Button, temp.X, temp.Y);
                }
            }
            else if (_eventQueue.Peek() is MouseUpEvent)
            {
                if (_gameHasStarted)
                {
                    MouseUpEvent temp = (MouseUpEvent)(_eventQueue.Peek());

                    if (_inputManager.MouseUp(temp.Button, temp.X, temp.Y))
                        return;

                    owner.MouseUp(temp.Button, temp.X, temp.Y);
                }
            }
            else if (_eventQueue.Peek() is KeyDownEvent)
            {
                if (_gameHasStarted)
                {
                    KeyDownEvent temp = (KeyDownEvent)(_eventQueue.Peek());

                    if (_inputManager.KeyDown(temp.Key, temp.Shift))
                        return;

                    owner.KeyDown(temp.Key, temp.Shift);
                }
            }
            else if (_eventQueue.Peek() is KeyUpEvent)
            {
                if (_gameHasStarted)
                {
                    KeyUpEvent temp = (KeyUpEvent)(_eventQueue.Peek());

                    if (_inputManager.KeyUp(temp.Key, temp.Shift))
                        return;

                    owner.KeyUp(temp.Key, temp.Shift);
                }
            }
            else if (_eventQueue.Peek() is ChatEvent)
            {

                ChatEvent temp = (ChatEvent)(_eventQueue.Peek());

                ChatManager.DisplayChatMessage(_players[temp.PlayerID], temp.ChatMessage);

            }
            else
            {
                throw new Exception("Error in ParseNextEvent, unknown event type...");
            }


        }

        void _server_OnReceiveData(object o)
        {

            if (o is GameEvent)
            {

                // Pass on to all clients
                _server.SendData(o);

            }

            if (o is MultiplayerRequest)
            {

                MultiplayerRequest request = (MultiplayerRequest)o;
                MultiplayerResponse response = new MultiplayerResponse();

                if (!_gameHasStarted)
                {

                    // Accept
                    response.AcceptedRequest = true;
                    response.TargetIP = request.MyIP;
                    response.PlayerNumber = _players.Count;
                    response.MapName = _mapName;
                    response.MapHash = _mapHash;

                    _multiplayerStatus.PlayerNames.Add(request.PlayerName);
                    _players.Add(new Player(this, response.PlayerNumber, request.PlayerName));
                    _players[response.PlayerNumber].InitializeFogArray(_map.WidthInTiles, _map.HeightInTiles);
                    AddPlayerToMap(response.PlayerNumber);

                    _chatManager.DisplayChatMessage(null, _multiplayerStatus.PlayerNames.Last() + " has joined the game.");

                }
                else
                {

                    response.AcceptedRequest = false;

                }

                _server.SendData(response);
                _server.SendData(_multiplayerStatus);

            }

        }

        void _client_OnReceiveData(object o)
        {


            if (o is GameEvent)
            {

                if (_gameHasStarted)
                {
                    _eventQueue.Enqueue((GameEvent)o);

                }
                else if (o is ChatEvent)
                {
                    _eventQueue.Enqueue((ChatEvent)o);
                }


            }

            if (!_isServer)
            {

                if (o is MultiplayerResponse)
                {

                    if (((MultiplayerResponse)o).TargetIP == _myLanIP)
                    {
                        _clientResponse = ((MultiplayerResponse)o);
                    }

                }

                if (o is MultiplayerStatus)
                {

                    _multiplayerStatus = ((MultiplayerStatus)o);

                    if (_multiplayerStatus.StartGame)
                    {
                        _gameHasStarted = true;
                    }
                    else
                    {

                        if (!_clientFirstStatus)
                        {

                            _players.Add(new Player(this, _multiplayerStatus.PlayerNames.Count - 1, _multiplayerStatus.PlayerNames.Last()));
                            _players[_multiplayerStatus.PlayerNames.Count - 1].InitializeFogArray(_map.WidthInTiles, _map.HeightInTiles);
                            AddPlayerToMap(_multiplayerStatus.PlayerNames.Count - 1);

                            _chatManager.DisplayChatMessage(null, _multiplayerStatus.PlayerNames.Last() + " has joined the game.");

                        }

                        _clientFirstStatus = false;

                    }
                }

            }
            else
            {

                if (o is MultiplayerStatus)
                {

                    _multiplayerStatus = ((MultiplayerStatus)o);

                    if (_multiplayerStatus.StartGame)
                    {
                        _gameHasStarted = true;
                    }

                }
            }

        }

        void PlaceStarter(int x, int y, Player forWho)
        {

            _unitManager.AddUnit(new TownCenter(x, y, forWho));
            _unitManager.AddUnit(new Laborer(x + 2, y - 1, forWho));
            _unitManager.AddUnit(new Laborer(x - 2, y - 1, forWho));
            _unitManager.AddUnit(new Laborer(x + 1, y + 2, forWho));

            if (forWho == _localPlayer)
            {
                Map.ScrollScreen((x * 64) - (Globals.iScreenWidth / 2) + 64, (y * 64) - (Globals.iScreenHeight / 2) + 64);
            }

        }

        public void RenderSelectedUnitHighlighter()
        {


            _sprSelected.Left = _localPlayer.SelectedUnit.SpriteX;
            _sprSelected.Top = _localPlayer.SelectedUnit.SpriteY;

            _sprSelected.Width = _localPlayer.SelectedUnit.SpriteWidth;
            _sprSelected.Height = _localPlayer.SelectedUnit.SpriteHeight;

            _sprSelected.Render(_texSelected);

        }

        void AddPlayerToMap(int playerID)
        {

            switch (playerID)
            {
                case 0:
                    PlaceStarter(5, 5, _players[0]);
                    break;
                case 1:
                    PlaceStarter(14, 20, _players[1]);
                    break;
                case 2:
                    PlaceStarter(24, 12, _players[2]);
                    break;
                case 3:
                    PlaceStarter(29, 21, _players[3]);
                    break;
                default:
                    break;
            }

        }

        public void StartMultiplayer()
        {

            //_gameHasStarted = true;

            
            //// Send object back to clients indicating to start
            _multiplayerStatus.StartGame = true;

            _server.SendData(_multiplayerStatus);

        }

        #endregion
    }
}
